package cc.shacocloud.mirage.utils.resource;

import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

/**
 * 将 URL 解析为文件引用的资源的抽象基类，例如  {@link ClassPathResource}
 */
public abstract class AbstractFileResolvingResource extends AbstractResource {
    
    /**
     * 设置 {@link URLConnection#setUseCaches} 标志，首选 {@code false}，但对于基于 JNLP 的资源，将标志保留为 {@code true}
     */
    public static void useCachesIfNecessary(@NotNull URLConnection con) {
        con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP"));
    }
    
    @Override
    public boolean exists() {
        try {
            URL url = getURL();
            if (ResourceUtil.isFileURL(url)) {
                // 继续文件系统解析
                return getFile().exists();
            } else {
                // 尝试使用 URL 连接内容长度标头
                URLConnection con = url.openConnection();
                customizeConnection(con);
                HttpURLConnection httpCon =
                        (con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
                if (httpCon != null) {
                    int code = httpCon.getResponseCode();
                    if (code == HttpURLConnection.HTTP_OK) {
                        return true;
                    } else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
                        return false;
                    }
                }
                if (con.getContentLengthLong() > 0) {
                    return true;
                }
                if (httpCon != null) {
                    // 没有 HTTP OK 状态，也没有内容长度标头：放弃
                    httpCon.disconnect();
                    return false;
                } else {
                    // 回到流的存在：我们可以打开流吗？
                    getStream().close();
                    return true;
                }
            }
        } catch (IOException ex) {
            return false;
        }
    }
    
    @Override
    public boolean isReadable() {
        try {
            return checkReadable(getURL());
        } catch (IOException ex) {
            return false;
        }
    }
    
    protected boolean checkReadable(URL url) {
        try {
            if (ResourceUtil.isFileURL(url)) {
                // 继续文件系统解析
                File file = getFile();
                return (file.canRead() && !file.isDirectory());
            } else {
                // 尝试 jar 资源的输入流解析
                URLConnection con = url.openConnection();
                customizeConnection(con);
                if (con instanceof HttpURLConnection) {
                    HttpURLConnection httpCon = (HttpURLConnection) con;
                    int code = httpCon.getResponseCode();
                    if (code != HttpURLConnection.HTTP_OK) {
                        httpCon.disconnect();
                        return false;
                    }
                }
                long contentLength = con.getContentLengthLong();
                if (contentLength > 0) {
                    return true;
                } else if (contentLength == 0) {
                    // 空文件或目录 ->不被视为可读...
                    return false;
                } else {
                    getStream().close();
                    return true;
                }
            }
        } catch (IOException ex) {
            return false;
        }
    }
    
    @Override
    public boolean isFile() {
        try {
            URL url = getURL();
            return ResourceUtil.URL_PROTOCOL_FILE.equals(url.getProtocol());
        } catch (IOException ex) {
            return false;
        }
    }
    
    /**
     * 此实现返回基础类路径资源的 File 引用，前提是它引用文件系统中的文件。
     */
    @Override
    public File getFile() throws IOException {
        URL url = getURL();
        return ResourceUtil.getFile(url, getDescription());
    }
    
    /**
     * 此实现确定基础文件（或 jar 文件，如果是 jarzip 中的资源）
     */
    @NotNull
    @Override
    protected File getFileForLastModifiedCheck() throws IOException {
        URL url = getURL();
        if (ResourceUtil.isJarURL(url)) {
            URL actualUrl = ResourceUtil.extractJarFileURL(url);
            return ResourceUtil.getFile(actualUrl, "Jar URL");
        } else {
            return getFile();
        }
    }
    
    @Override
    public long contentLength() throws IOException {
        URL url = getURL();
        if (ResourceUtil.isFileURL(url)) {
            // 继续文件系统解析
            File file = getFile();
            long length = file.length();
            if (length == 0L && !file.exists()) {
                throw new FileNotFoundException(getDescription() + " 无法在文件系统中解析以检查其内容长度");
            }
            return length;
        } else {
            // 尝试使用 URL 连接内容长度标头
            URLConnection con = url.openConnection();
            customizeConnection(con);
            return con.getContentLengthLong();
        }
    }
    
    @Override
    public long lastModified() throws IOException {
        URL url = getURL();
        boolean fileCheck = false;
        if (ResourceUtil.isFileURL(url) || ResourceUtil.isJarURL(url)) {
            // 继续文件系统解析
            fileCheck = true;
            try {
                File fileToCheck = getFileForLastModifiedCheck();
                long lastModified = fileToCheck.lastModified();
                if (lastModified > 0L || fileToCheck.exists()) {
                    return lastModified;
                }
            } catch (FileNotFoundException ex) {
                // 防御性地回退到 URL 连接检查
            }
        }
        URLConnection con = url.openConnection();
        customizeConnection(con);
        long lastModified = con.getLastModified();
        if (fileCheck && lastModified == 0 && con.getContentLengthLong() <= 0) {
            throw new FileNotFoundException(getDescription() + " 无法在文件系统中解析以检查其上次修改的时间戳");
        }
        return lastModified;
    }
    
    protected void customizeConnection(URLConnection con) throws IOException {
        useCachesIfNecessary(con);
        if (con instanceof HttpURLConnection) {
            customizeConnection((HttpURLConnection) con);
        }
    }
    
    protected void customizeConnection(@NotNull HttpURLConnection con) throws IOException {
        con.setRequestMethod("HEAD");
    }
    
}
